from __future__ import absolute_import

import json
import six

from collections import OrderedDict
from django import forms

from sentry.rules.conditions.base import EventCondition


class MatchType(object):
    EQUAL = 'eq'
    NOT_EQUAL = 'ne'
    STARTS_WITH = 'sw'
    ENDS_WITH = 'ew'
    CONTAINS = 'co'
    NOT_CONTAINS = 'nc'
    IS_SET = 'is'
    NOT_SET = 'ns'


MATCH_CHOICES = OrderedDict(
    [
        (MatchType.EQUAL, 'equals'),
        (MatchType.NOT_EQUAL, 'does not equal'),
        (MatchType.STARTS_WITH, 'starts with'),
        (MatchType.ENDS_WITH, 'ends with'),
        (MatchType.CONTAINS, 'contains'),
        (MatchType.NOT_CONTAINS, 'does not contain'),
        (MatchType.IS_SET, 'is set'),
        (MatchType.NOT_SET, 'is not set'),
    ]
)

ATTR_CHOICES = [
    'message',
    'platform',
    'environment',
    'type',
    'exception.type',
    'exception.value',
    'user.id',
    'user.email',
    'user.username',
    'user.ip_address',
    'http.method',
    'http.url',
    'stacktrace.code',
    'stacktrace.module',
    'stacktrace.filename',
]


class FixedTypeaheadInput(forms.TextInput):
    def __init__(self, choices, *args, **kwargs):
        super(FixedTypeaheadInput, self).__init__(*args, **kwargs)
        self.attrs['data-choices'] = json.dumps(choices)
        self.attrs['class'] = self.attrs.get('class', '') + ' typeahead'


class EventAttributeForm(forms.Form):
    attribute = forms.CharField(
        widget=FixedTypeaheadInput(
            choices=[{
                'id': a,
                'text': a
            } for a in ATTR_CHOICES],
        )
    )
    match = forms.ChoiceField(
        MATCH_CHOICES.items()
    )
    value = forms.CharField(
        widget=forms.TextInput(), required=False
    )


class EventAttributeCondition(EventCondition):
    """
    Attributes are a mapping of <logical-key>.<property>.

    For example:

    - message
    - platform
    - exception.{type,value}
    - user.{id,ip_address,email,FIELD}
    - http.{method,url}
    - stacktrace.{code,module,filename}
    - extra.{FIELD}
    """
    # TODO(dcramer): add support for stacktrace.vars.[name]

    form_cls = EventAttributeForm
    label = u'An event\'s {attribute} value {match} {value}'

    form_fields = {
        'attribute': {
            'type': 'choice',
            'placeholder': 'i.e. exception.type',
            'choices': [[a, a] for a in ATTR_CHOICES]
        },
        'match': {
            'type': 'choice',
            'choices': MATCH_CHOICES.items()
        },
        'value': {
            'type': 'string',
            'placeholder': 'value'
        }
    }

    def _get_attribute_values(self, event, attr):
        # TODO(dcramer): we should validate attributes (when we can) before
        path = attr.split('.')

        if path[0] == 'platform':
            if len(path) != 1:
                return []
            return [event.platform]

        if path[0] == 'message':
            if len(path) != 1:
                return []
            return [event.real_message]

        elif path[0] == 'environment':
            return [event.get_tag('environment')]

        elif path[0] == 'type':
            return [event.data['type']]

        elif len(path) == 1:
            return []

        elif path[0] == 'extra':
            path.pop(0)
            value = event.data['extra']
            while path:
                bit = path.pop(0)
                value = value.get(bit)
                if not value:
                    return []

            if isinstance(value, (list, tuple)):
                return value
            return [value]

        elif len(path) != 2:
            return []

        elif path[0] == 'exception':
            if path[1] not in ('type', 'value'):
                return []

            return [
                getattr(e, path[1]) for e in event.interfaces['exception'].values
            ]

        elif path[0] == 'user':
            if path[1] in ('id', 'ip_address', 'email', 'username'):
                return [getattr(event.interfaces['user'], path[1])]
            return [getattr(event.interfaces['user'].data, path[1])]

        elif path[0] == 'http':
            if path[1] not in ('url', 'method'):
                return []

            return [getattr(event.interfaces['request'], path[1])]

        elif path[0] == 'stacktrace':
            stacks = event.interfaces.get('stacktrace')
            if stacks:
                stacks = [stacks]
            else:
                stacks = [
                    e.stacktrace for e in event.interfaces['exception'].values
                    if e.stacktrace
                ]

            result = []
            for st in stacks:
                for frame in st.frames:
                    if path[1] in ('filename', 'module'):
                        result.append(getattr(frame, path[1]))
                    elif path[1] == 'code':
                        if frame.pre_context:
                            result.extend(frame.pre_context)
                        if frame.context_line:
                            result.append(frame.context_line)
                        if frame.post_context:
                            result.extend(frame.post_context)
            return result
        return []

    def render_label(self):
        data = {
            'attribute': self.data['attribute'],
            'value': self.data['value'],
            'match': MATCH_CHOICES[self.data['match']],
        }
        return self.label.format(**data)

    def passes(self, event, state, **kwargs):
        attr = self.get_option('attribute')
        match = self.get_option('match')
        value = self.get_option('value')

        if not (attr and match and value):
            return False

        value = value.lower()
        attr = attr.lower()

        try:
            attribute_values = self._get_attribute_values(event, attr)
        except KeyError:
            attribute_values = []

        attribute_values = [
            six.text_type(v).lower()
            for v in attribute_values
            if v is not None
        ]

        if match == MatchType.EQUAL:
            for a_value in attribute_values:
                if a_value == value:
                    return True
            return False

        elif match == MatchType.NOT_EQUAL:
            for a_value in attribute_values:
                if a_value == value:
                    return False
            return True

        elif match == MatchType.STARTS_WITH:
            for a_value in attribute_values:
                if a_value.startswith(value):
                    return True
            return False

        elif match == MatchType.ENDS_WITH:
            for a_value in attribute_values:
                if a_value.endswith(value):
                    return True
            return False

        elif match == MatchType.CONTAINS:
            for a_value in attribute_values:
                if value in a_value:
                    return True
            return False

        elif match == MatchType.NOT_CONTAINS:
            for a_value in attribute_values:
                if value in a_value:
                    return False
            return True

        elif match == MatchType.IS_SET:
            return bool(attribute_values)

        elif match == MatchType.NOT_SET:
            return not attribute_values
